﻿using System.Runtime.Intrinsics.X86;
using Daemon.Common;
using Daemon.Data.Substructure.Enums;
using Daemon.Model;
using Daemon.Repository.Contract;
using Daemon.Service.Contract;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Daemon.Data.Infrastructure.Auth;
using Daemon.Middleware;
using Daemon.Common.Auth;
using Daemon.Service.Contract.Jwt;
using Daemon.Model.Jwt;
using Daemon.Common.Attribute;
using Daemon.Common.Cache;
using Daemon.Common.Const;
using Daemon.Repository.EF;
using DaemonAPI.Helpers;
using System.ComponentModel.DataAnnotations;
using Daemon.Data.Infrastructure.Configuration;

namespace DaemonAPI.Controllers
{
	[ApiController]
	[Route("api/authenticate")]
	public class AuthV2Controller : BaseApiController
	{
		private readonly IAuthInfoService _authInfoService;

		private readonly IHttpContextAccessor _httpContextAccessor;

		private readonly IUserSessionRepository _userSessionRepository;

		private readonly IUserAuthorizationService _userAuthorizationService;

		private readonly IJwtAppService _jwtAppService;

		private readonly IClientConfigRepository _clientConfigRepository;

		private readonly IDaemonDistributedCache _cache;

		private readonly IDaemonProductService _daemonProductService;

		private readonly IApplicationRepository _applicationRepository;

		private readonly SessionItemRepository _sessionItemRepository;

		private readonly IContextInfoProvider _contextInfoProvider;

		public AuthV2Controller(IDaemonDistributedCache cache,
			IClientConfigRepository clientConfigRepository,
			IJwtAppService jwtAppService,
			IAuthInfoService authInfoService,
			IUserAuthorizationService userAuthorizationService,
			IUserSessionRepository userSessionRepository,
			IHttpContextAccessor httpContextAccessor,
			IDaemonProductService daemonProductService,
			IApplicationRepository applicationRepository,
			SessionItemRepository sessionItemRepository,
			IContextInfoProvider contextInfoProvider)
		{
			_authInfoService = authInfoService;
			_httpContextAccessor = httpContextAccessor;
			_userSessionRepository = userSessionRepository;
			_userAuthorizationService = userAuthorizationService;
			_jwtAppService = jwtAppService;
			_clientConfigRepository = clientConfigRepository;
			_sessionItemRepository = sessionItemRepository;
			_daemonProductService = daemonProductService;
			_applicationRepository = applicationRepository;
			_contextInfoProvider = contextInfoProvider;
			_cache = cache;
		}

		[AllowAnonymous]
		[HttpGet]
		[Route(nameof(AuthKey))]
		public ResultModel AuthKey()
		{
			var resultStr = string.Empty;
			string authKey = _cache.Get<string>(CacheKeyConst.AUTH_KEY);
			if (!string.IsNullOrEmpty(authKey))
			{
				resultStr = PasswordHelper.Rfc2898Encrypt(authKey, "daemon");
				return new ResultModel(HttpStatusCode.OK, null, new List<string>() { resultStr });
			}

			authKey = _clientConfigRepository.FindAll().FirstOrDefault()?.AuthKey;
			_cache.Set(CacheKeyConst.AUTH_KEY, authKey);
			resultStr = PasswordHelper.Rfc2898Encrypt(authKey, "daemon");
			return new ResultModel(HttpStatusCode.OK, null, new List<string>() { resultStr });
		}

		[Route(nameof(TestLogin))]
		[HttpPost]
		[AllowAnonymous]
		public async Task<ResultModel> TestLogin()
		{
			var username = "daemon";
			var prefix = "sf";
			var password = "123456";
			var confirmLogged = false;
			username = username.Trim().ToLowerInvariant();
			(BaseAuthUser user, HttpStatusCode state, string message) = _authInfoService.GetLoginUser(prefix, username, password);
			switch (state)
			{
				case HttpStatusCode.NotFound:
					return new ResultModel(HttpStatusCode.NotFound, message);
				case HttpStatusCode.Unauthorized:
					return new ResultModel(HttpStatusCode.Unauthorized, message);
			}

			if (user != null && !MFAHelper.IsEnableEmailAuth(user) && confirmLogged && _sessionItemRepository.CheckUserIsOnline(user.Id))
			{
				return new ResultModel(HttpStatusCode.PreconditionFailed, "Logined");
			}

			if (user != null)
			{
				if (!MFAHelper.IsEnableEmailAuth(user))
				{
					_sessionItemRepository.DeleteByUserId(user.Id, LogoutReasonEnum.LoggedInOnOtherDevice);
				}
			}

			var tokenResult = TokenHelper.CreateToken(username, prefix, user);
			AddLoginInfo(user);
			var authInfo = new AuthInfoEF(JsonConvert.DeserializeObject<BaseAuthUser>(JsonConvert.SerializeObject(_userAuthorizationService.GetUserEntity(username, null))));
			ServiceLocator.Resolve<ClientAuthCache>().AddClientUserEntryAsync(prefix + "_" + username, authInfo);
			return new ResultModel(HttpStatusCode.OK, "", new List<JwtAuthorizationDto>() { new JwtAuthorizationDto() { Token = tokenResult } });
		}

		[Route(nameof(Login))]
		[HttpPost]
		[AllowAnonymous]
		public async Task<ResultModel> Login([Required] string prefix, [FromQuery] string username, [FromBody] string password, bool confirmLogged = false)
		{
			username = username.Trim().ToLowerInvariant();
			(BaseAuthUser user, HttpStatusCode state, string message) = _authInfoService.GetLoginUser(prefix, username, password);
			switch (state)
			{
				case HttpStatusCode.NotFound:
					return new ResultModel(HttpStatusCode.NotFound, message);
				case HttpStatusCode.Unauthorized:
					return new ResultModel(HttpStatusCode.Unauthorized, message);
			}

			if (user != null && !MFAHelper.IsEnableEmailAuth(user) && confirmLogged && _sessionItemRepository.CheckUserIsOnline(user.Id))
			{
				return new ResultModel(HttpStatusCode.PreconditionFailed, "Logined");
			}

			if (user != null)
			{
				if (!MFAHelper.IsEnableEmailAuth(user))
				{
					_sessionItemRepository.DeleteByUserId(user.Id, LogoutReasonEnum.LoggedInOnOtherDevice);
				}
			}

			var tokenResult = TokenHelper.CreateToken(username, prefix, user);
			AddLoginInfo(user);
			var authInfo = new AuthInfoEF(JsonConvert.DeserializeObject<BaseAuthUser>(JsonConvert.SerializeObject(_userAuthorizationService.GetUserEntity(username, null))));
			ServiceLocator.Resolve<ClientAuthCache>().AddClientUserEntryAsync(username, authInfo);
			return new ResultModel(HttpStatusCode.OK, "", new List<JwtAuthorizationDto>() { new JwtAuthorizationDto() { Token = tokenResult } });
		}

		[Route(nameof(AuthInfo))]
		[HttpGet]
		[AllowAnonymous]
		public ResultModel<IAuthInfo> AuthInfo()
		{
			IAuthInfo authInfo = ServiceLocator.Resolve<ClientAuthCache>().GetAuthInfoByClientUser(UserName);
			_contextInfoProvider.AuthInfo = authInfo;
			return new ResultModel<IAuthInfo>(HttpStatusCode.OK, authInfo, null);
		}

		private void AddLoginInfo(BaseAuthUser user)
		{
			var session = new UserSession()
			{
				UserId = user.Id,
				IP = HttpContextExtension.GetClientIPAddress(_httpContextAccessor.HttpContext.Request),
				LoginTime = DateTime.UtcNow,
			};

			_userSessionRepository.SetLogoutTimeByUser(user.Id, LogoutReasonEnum.LoggedInOnOtherDevice); // To handle do login on same time, mark the older one Log out
			_userSessionRepository.AddByDBContext(session);
		}
	}
}
